package decaf.typecheck;

import java.util.Stack;

import decaf.Driver;
import decaf.tree.Tree;
import decaf.error.*;
import decaf.scope.*;
import decaf.symbol.Class;
import decaf.symbol.Function;
import decaf.symbol.Symbol;
import decaf.symbol.Variable;
import decaf.type.*;

public class TypeCheck extends Tree.Visitor 
{
	private ScopeStack table;

	private Stack<Tree> breaks;

	private Function currentFunction;

	public TypeCheck(ScopeStack table) 
	{
		this.table = table;
		breaks = new Stack<Tree>();		
	}

	public static void checkType(Tree.TopLevel tree) 
	{		
		new TypeCheck(Driver.getDriver().getTable()).visitTopLevel(tree);
	}

	@Override
	public void visitBinary(Tree.Binary expr) 
	{
		/**
		Tree.PLUS Tree.MINUS Tree.MUL Tree.DIV Tree.MOD
		Tree.EQ Tree.NE
		Tree.LT Tree.GT Tree.LE Tree.GE
		Tree.AND Tree.OR
		*/
		expr.left.accept(this);	
		expr.right.accept(this);
		if(expr.tag == Tree.PLUS || expr.tag == Tree.MINUS || expr.tag == Tree.MUL || expr.tag == Tree.DIV || expr.tag == Tree.MOD)
		{
			if (expr.left.type.equal(BaseType.INT) && expr.right.type.equal(BaseType.INT))
			{
				expr.type = BaseType.INT;
			}
			else if(	expr.left.type.equal(BaseType.INT) && expr.right.type.equal(BaseType.ERROR) ||
						expr.left.type.equal(BaseType.ERROR) && expr.right.type.equal(BaseType.INT) ||
						expr.left.type.equal(BaseType.ERROR) && expr.right.type.equal(BaseType.ERROR) )
			{
				expr.type = BaseType.ERROR;
			}
			else 
			{
				if (expr.tag == Tree.PLUS)
				{
					issueError(new IncompatBinOpError(expr.getLocation(), expr.left.type.toString(),"+", expr.right.type.toString()));
					expr.type = BaseType.ERROR;
				}
				else if(expr.tag == Tree.MINUS)
				{
					issueError(new IncompatBinOpError(expr.getLocation(), expr.left.type.toString(),"-", expr.right.type.toString()));
					expr.type = BaseType.ERROR;
				}
				else if(expr.tag == Tree.MUL)
				{
					issueError(new IncompatBinOpError(expr.getLocation(), expr.left.type.toString(),"*", expr.right.type.toString()));
					expr.type = BaseType.ERROR;
				}
				else if(expr.tag == Tree.DIV)
				{
					issueError(new IncompatBinOpError(expr.getLocation(), expr.left.type.toString(),"/", expr.right.type.toString()));
					expr.type = BaseType.ERROR;
				}		
				else
				{
					issueError(new IncompatBinOpError(expr.getLocation(), expr.left.type.toString(),"%", expr.right.type.toString()));
					expr.type = BaseType.ERROR;					
				}
			}
		}
		else if (expr.tag == Tree.EQ || expr.tag == Tree.NE)
		{
			/**
			 * 	二元判等操作符（!= ，==）的两个操作数必须是等价类型（例如两个int，但请参考下
			 *	面关于对象判等的例外情况），结果类型是bool。
			 *	二元判等操作符的两个操作数也可以是两个对象或者一个对象和null，
			 * 	两个对象的类型必须在至少一个方向上兼容，结果类型是bool。 
			 * */
			if(expr.left.type.equal(expr.right.type) || expr.left.type.compatible(expr.right.type) || expr.right.type.compatible(expr.left.type))
			{
				expr.type = BaseType.BOOL;
			}
			else if(expr.left.type.equal(BaseType.ERROR) || expr.right.type.equal(BaseType.ERROR))
			{
				expr.type = BaseType.ERROR;
			}
			else
			{
				expr.type = BaseType.ERROR;
			}
		}
		else if (expr.tag == Tree.LT || expr.tag == Tree.GT || expr.tag == Tree.LE || expr.tag == Tree.GE)
		{
			if (expr.left.type.equal(BaseType.INT) && expr.right.type.equal(BaseType.INT))
			{
				expr.type = BaseType.BOOL;
			}
			else if(	expr.left.type.equal(BaseType.INT) && expr.right.type.equal(BaseType.ERROR) ||
						expr.left.type.equal(BaseType.ERROR) && expr.right.type.equal(BaseType.INT) ||
						expr.left.type.equal(BaseType.ERROR) && expr.right.type.equal(BaseType.ERROR) )
			{
				expr.type = BaseType.ERROR;
			}
			else 
			{
				if (expr.tag == Tree.LT)
				{
					issueError(new IncompatBinOpError(expr.getLocation(), expr.left.type.toString(),"<", expr.right.type.toString()));
					expr.type = BaseType.ERROR;					
				}
				else if(expr.tag == Tree.GT)
				{
					issueError(new IncompatBinOpError(expr.getLocation(), expr.left.type.toString(),">", expr.right.type.toString()));
					expr.type = BaseType.ERROR;					
				}
				else if(expr.tag == Tree.LE)
				{
					issueError(new IncompatBinOpError(expr.getLocation(), expr.left.type.toString(),"<=", expr.right.type.toString()));
					expr.type = BaseType.ERROR;					
				}
				else
				{
					issueError(new IncompatBinOpError(expr.getLocation(), expr.left.type.toString(),">=", expr.right.type.toString()));
					expr.type = BaseType.ERROR;					
				}		
			}		
		}
		else
		{
			/**else if (expr.tag == Tree.AND || expr.tag == Tree.OR)*/
			/**&&和||不使用布尔短路；在计算结果之前两个表达式都需要求值。*/
			if (expr.left.type.equal(BaseType.BOOL) && expr.right.type.equal(BaseType.BOOL))
			{
				expr.type = BaseType.BOOL;
			}
			else if(	expr.left.type.equal(BaseType.BOOL) && expr.right.type.equal(BaseType.ERROR) ||
						expr.left.type.equal(BaseType.ERROR) && expr.right.type.equal(BaseType.BOOL) ||
						expr.left.type.equal(BaseType.ERROR) && expr.right.type.equal(BaseType.ERROR) )
			{
				expr.type = BaseType.ERROR;
			}
			else 
			{
				if (expr.tag == Tree.AND)
				{
					issueError(new IncompatBinOpError(expr.getLocation(), expr.left.type.toString(),"&&", expr.right.type.toString()));
					expr.type = BaseType.ERROR;					
				}
				else
				{
					issueError(new IncompatBinOpError(expr.getLocation(), expr.left.type.toString(),"||", expr.right.type.toString()));
					expr.type = BaseType.ERROR;					
				}
			}
		}	
	}

	@Override
	public void visitUnary(Tree.Unary expr) 
	{
		expr.expr.accept(this);
		if(expr.tag == Tree.NEG)
		{
			if (expr.expr.type.equal(BaseType.ERROR) || expr.expr.type.equal(BaseType.INT)) 
			{
				expr.type = expr.expr.type;
			} 
			else 
			{
				issueError(new IncompatUnOpError(expr.getLocation(), "-", expr.expr.type.toString()));
				expr.type = BaseType.ERROR;
			}
		}
		else
		{
			if (expr.expr.type.equal(BaseType.ERROR) || expr.expr.type.equal(BaseType.BOOL))
			{
				expr.type = expr.expr.type;
			} 
			else 
			{
				issueError(new IncompatUnOpError(expr.getLocation(), "!", expr.expr.type.toString()));
				expr.type = BaseType.ERROR;
			}
		}
	}

	@Override
	public void visitLiteral(Tree.Literal literal) 
	{
		if(literal.typeTag == Tree.INT)
			literal.type = BaseType.INT;
		else if(literal.typeTag == Tree.BOOL)
			literal.type = BaseType.BOOL;
		else
			literal.type = BaseType.STRING;
	}

	@Override
	public void visitNull(Tree.Null nullExpr) 
	{
		nullExpr.type = BaseType.NULL;
	}

	@Override
	public void visitReadIntExpr(Tree.ReadIntExpr readIntExpr) 
	{
		readIntExpr.type = BaseType.INT;
	}

	@Override
	public void visitReadLineExpr(Tree.ReadLineExpr readStringExpr) 
	{
		readStringExpr.type = BaseType.STRING;
	}

	@Override
	public void visitIndexed(Tree.Indexed indexed) 
	{
		indexed.lvKind = Tree.LValue.Kind.ARRAY_ELEMENT;
		indexed.array.accept(this);
		if (!indexed.array.type.isArrayType()) 
		{
			issueError(new NotArrayError(indexed.array.getLocation()));
			indexed.type = BaseType.ERROR;
		} 
		else 
		{
			indexed.type = ((ArrayType) indexed.array.type).getElementType();
		}
		indexed.index.accept(this);
		if (!indexed.index.type.equal(BaseType.INT)) 
		{
			issueError(new SubNotIntError(indexed.getLocation()));
		}
	}

	@Override
	public void visitCallExpr(Tree.CallExpr callExpr) 
	{				
		if(callExpr.receiver != null)
		{
			callExpr.receiver.accept(this);			
			if(!callExpr.receiver.type.equal(BaseType.ERROR))
			{				
				if(callExpr.method.equals("length"))	
				{
					if(callExpr.receiver.type.isArrayType())
					{
						callExpr.type = BaseType.INT;
						callExpr.symbol = new Function(false,"length",callExpr.type,null,callExpr.getLocation());
					}
					else if(callExpr.receiver.type.isClassType())
					{
						issueError(new FieldNotFoundError(callExpr.getLocation(),callExpr.method,currentFunction.getScope().getOwner().getType().toString()));
						callExpr.type = BaseType.ERROR;
					}
					else
					{
						issueError(new BadLengthError(callExpr.getLocation()));
						callExpr.type = BaseType.ERROR;						
					}	
				}
				else
				{
					ClassScope cs ;
					if(callExpr.receiver.isClass)
					{
						cs = ((ClassType)callExpr.receiver.type).getClassScope();
						Symbol f = cs.lookupVisible(callExpr.method);
						if (f == null)
						{
							issueError(new FieldNotFoundError(callExpr.getLocation(),callExpr.method,currentFunction.getScope().getOwner().getType().toString()));
							callExpr.type = BaseType.ERROR;
						}
						else if(!f.isFunction())
						{
							issueError(new NotClassMethodError(callExpr.getLocation(),callExpr.method,currentFunction.getScope().getOwner().getType().toString()));
							callExpr.type = BaseType.ERROR;														
						}
						else
						{
							callExpr.symbol = (Function)f;
							if(callExpr.symbol.isStatik())							
							{
								callExpr.type = f.getType();
							}
							else
							{	
								issueError(new NotClassFieldError(callExpr.getLocation(),callExpr.method,currentFunction.getScope().getOwner().getType().toString()));
								callExpr.type = BaseType.ERROR;						
							}
						}																	
					}
					else if(callExpr.receiver.type.isClassType())
					{
						cs = ((ClassType)callExpr.receiver.type).getClassScope();
						Symbol f = cs.lookupVisible(callExpr.method);
						if (f == null)
						{
							issueError(new FieldNotFoundError(callExpr.getLocation(),callExpr.method,currentFunction.getScope().getOwner().getType().toString()));
							callExpr.type = BaseType.ERROR;
						}
						else if(!f.isFunction())
						{
							issueError(new NotClassMethodError(callExpr.getLocation(),callExpr.method,currentFunction.getScope().getOwner().getType().toString()));
							callExpr.type = BaseType.ERROR;														
						}
						else
						{
							callExpr.symbol = (Function)f;
							callExpr.type = f.getType();
						}											
					}
					else
					{
						cs = ((ClassType)(((FuncType)callExpr.receiver.type).getReturnType())).getClassScope();	
						Symbol f = cs.lookupVisible(callExpr.method);
						if (f == null)
						{
							issueError(new FieldNotFoundError(callExpr.getLocation(),callExpr.method,currentFunction.getScope().getOwner().getType().toString()));
							callExpr.type = BaseType.ERROR;
						}
						else if(!f.isFunction())
						{
							issueError(new NotClassMethodError(callExpr.getLocation(),callExpr.method,currentFunction.getScope().getOwner().getType().toString()));
							callExpr.type = BaseType.ERROR;							
						}
						else
						{
							callExpr.symbol = (Function)f;						
							callExpr.type = f.getType();
						}						
					}	
				}
			}
			else
			{
				callExpr.type = BaseType.ERROR;
			}
		}
		else
		{
			ClassScope cs = currentFunction.getScope();
			Symbol f = cs.lookupVisible(callExpr.method);
			if (f == null) 
			{
				issueError(new FieldNotFoundError(callExpr.getLocation(),callExpr.method, cs.getOwner().getType().toString()));
				callExpr.type = BaseType.ERROR;
			} 
			else
			{
				if(!((Function)f).isStatik() && currentFunction.isStatik())
				{
					issueError(new RefNonStaticError(callExpr.getLocation(),currentFunction.getName(),callExpr.method));
					callExpr.type = BaseType.ERROR;
				}
				else 
				{
					callExpr.symbol = (Function)f;									
					callExpr.type = f.getType();
				}
			}
		}
		if(callExpr.type != BaseType.ERROR)
		{
			table.open(callExpr.symbol.getAssociatedScope());
			int i = 0;
			for (Tree.Expr f : callExpr.actuals) 
			{
				i++;
			}
			table.close();
			if(callExpr.method.equals("length"))
			{
				if(i != 0)
				{	
					issueError(new BadArgCountError(callExpr.getLocation(),callExpr.method,0,i));
					callExpr.type = BaseType.ERROR;												
				}
			}
			else if(!callExpr.symbol.isStatik() && callExpr.receiver == null && i + 1 != ((FuncType)callExpr.type).numOfParams())
			{
				issueError(new BadArgCountError(callExpr.getLocation(),callExpr.method,((FuncType)callExpr.type).numOfParams(),i));
				callExpr.type = BaseType.ERROR;								
			}
			else if(callExpr.symbol.isStatik() && callExpr.receiver == null && i != ((FuncType)callExpr.type).numOfParams())
			{
				issueError(new BadArgCountError(callExpr.getLocation(),callExpr.method,((FuncType)callExpr.type).numOfParams(),i));
				callExpr.type = BaseType.ERROR;				
			}
			else if(callExpr.receiver != null && !callExpr.receiver.isClass && i + 1 != ((FuncType)callExpr.type).numOfParams())
			{
				issueError(new BadArgCountError(callExpr.getLocation(),callExpr.method,((FuncType)callExpr.type).numOfParams(),i));
				callExpr.type = BaseType.ERROR;				
			}
			else if(callExpr.receiver != null && callExpr.receiver.isClass && i != ((FuncType)callExpr.type).numOfParams())
			{
				issueError(new BadArgCountError(callExpr.getLocation(),callExpr.method,((FuncType)callExpr.type).numOfParams(),i));
				callExpr.type = BaseType.ERROR;
			}
			else
			{
				table.open(callExpr.symbol.getAssociatedScope());
				if(callExpr.receiver == null && callExpr.symbol.isStatik() || callExpr.receiver != null && callExpr.receiver.isClass)
					i = 0;
				else
					i = 1;
				for (Tree.Expr e : callExpr.actuals) 
				{
					e.accept(this);
					if(!e.type.compatible(((FuncType)callExpr.type).getArgList().get(i)))
					{
						issueError(new BadArgTypeError(e.getLocation(),i+1,e.type.toString(),((FuncType)callExpr.type).getArgList().get(i).toString()));
						callExpr.type = BaseType.ERROR;
						break;
					}
					i++;			
				}
				table.close();
			}
		}
		if(!callExpr.method.equals("length") && callExpr.type != BaseType.ERROR)
		{
			callExpr.type = callExpr.symbol.getReturnType();
		}
	}

	@Override
	public void visitExec(Tree.Exec exec)
	{
		exec.expr.accept(this);
	}
	
	@Override
	public void visitNewArray(Tree.NewArray newArrayExpr) 
	{
		newArrayExpr.elementType.accept(this);
		newArrayExpr.length.accept(this);
		if(newArrayExpr.elementType.type.equal(BaseType.VOID))
		{
			issueError(new BadArrElementError(newArrayExpr.getLocation()));
			newArrayExpr.type = BaseType.ERROR;
		}
		else if(!newArrayExpr.length.type.equal(BaseType.INT))
		{
			issueError(new BadNewArrayLength(newArrayExpr.length.getLocation()));
			newArrayExpr.type = BaseType.ERROR;
		}
		else
		{
			newArrayExpr.type = new ArrayType(newArrayExpr.elementType.type);
		}
	}

	@Override
	public void visitNewClass(Tree.NewClass newClass) 
	{
		Class c = table.lookupClass(newClass.className);
		newClass.symbol = c;
		if (c == null) 
		{
			issueError(new ClassNotFoundError(newClass.getLocation(), newClass.className));
			newClass.type = BaseType.ERROR;
		} 
		else 
		{
			newClass.type = c.getType();
		}
	}

	@Override
	public void visitThisExpr(Tree.ThisExpr thisExpr) 
	{
		//?????????
		ClassScope cs = (ClassScope) table.lookForScope(Scope.Kind.CLASS);
		if (currentFunction.isStatik())
		{
			issueError(new ThisInStaticFuncError(thisExpr.getLocation()));
			thisExpr.type = BaseType.ERROR;
		}
		else
		{
			thisExpr.type = cs.getOwner().getType();
		}
	}

	@Override
	public void visitTypeTest(Tree.TypeTest instanceofExpr) 
	{
		instanceofExpr.instance.accept(this);
		Class c = table.lookupClass(instanceofExpr.className);
		instanceofExpr.symbol = c;
		if (c == null) 
		{
			issueError(new ClassNotFoundError(instanceofExpr.getLocation(),instanceofExpr.className));
			instanceofExpr.type = BaseType.ERROR;
		}
		else if(!instanceofExpr.instance.type.isClassType())
		{
			issueError(new NotClassError(instanceofExpr.instance.type.toString(),instanceofExpr.getLocation()));
			instanceofExpr.type = BaseType.ERROR;
		}
		else 
		{
			instanceofExpr.type = BaseType.BOOL;
		}		
	}

	@Override
	public void visitTypeCast(Tree.TypeCast cast) 
	{
		cast.expr.accept(this);
		if (!cast.expr.type.isClassType()) 
		{
			issueError(new NotClassError(cast.expr.type.toString(),cast.getLocation()));
		}
		Class c = table.lookupClass(cast.className);
		cast.symbol = c;
		if (c == null) 
		{
			issueError(new ClassNotFoundError(cast.getLocation(),cast.className));
			cast.type = BaseType.ERROR;
		} 
		else 
		{
			cast.type = c.getType();
		}
	}

	@Override
	public void visitIdent(Tree.Ident ident) 
	{
		if (ident.owner == null) 
		{
			ClassScope cs = currentFunction.getScope();
			Symbol v = cs.lookupVisible(ident.name);
			Symbol f = table.lookup(ident.name,true);
			if (v == null && f == null)
			{
				issueError(new FieldNotFoundError(ident.getLocation(),ident.name, ((ClassScope)table.lookForScope(Scope.Kind.CLASS)).getOwner().getType().toString()));
				ident.type = BaseType.ERROR;
			}
			else if(f == null)
			{
				if (v.isVariable())
				{
					ident.type = v.getType();
					ident.symbol = (Variable) v;
					ident.lvKind = Tree.LValue.Kind.MEMBER_VAR;					
				}
				else 
				{
					ident.type = v.getType();
					ident.isClass = true;
				}
			}
			else
			{
				if (f.isVariable())
				{
					ident.type = f.getType();
					ident.symbol = (Variable)f;
					ident.lvKind = Tree.LValue.Kind.LOCAL_VAR;
				}
				else 
				{
					ident.type = f.getType();
					ident.isClass = true;					
				}										
			}
		} 
		else
		{
			ident.owner.usedForRef = true;
			ident.owner.accept(this);
			if (!ident.owner.type.equal(BaseType.ERROR)) 
			{
				if (ident.owner.isClass || !ident.owner.type.isClassType()) 
				{
					issueError(new NotClassFieldError(ident.getLocation(), ident.name, ident.owner.type.toString()));
					ident.type = BaseType.ERROR;
				} 
				else 
				{
					ClassScope cs = ((ClassType) ident.owner.type).getClassScope();
					Symbol v = cs.lookupVisible(ident.name);
					if (v == null) 
					{
						issueError(new FieldNotFoundError(ident.getLocation(),ident.name, ident.owner.type.toString()));
						ident.type = BaseType.ERROR;
					} 
					else if (v.isVariable()) 
					{
						ClassType thisType = ((ClassScope) table.lookForScope(Scope.Kind.CLASS)).getOwner().getType();
						ident.type = v.getType();
						if (!thisType.compatible(ident.owner.type))
						{
							issueError(new FieldNotAccessError(ident.getLocation(), ident.name, ident.owner.type.toString()));
						} 
						else 
						{
							ident.symbol = (Variable) v;
							ident.lvKind = Tree.LValue.Kind.MEMBER_VAR;
						}
					} 
					else 
					{
						ident.type = v.getType();
					}
				}
			} 
			else 
			{
				ident.type = BaseType.ERROR;
			}
		}
	}

	@Override
	public void visitClassDef(Tree.ClassDef classDef) 
	{
		table.open(classDef.symbol.getAssociatedScope());
		for (Tree f : classDef.fields) 
		{
			f.accept(this);
		}
		table.close();
	}

	@Override
	public void visitMethodDef(Tree.MethodDef func) 
	{
		this.currentFunction = func.symbol;
		table.open(func.symbol.getAssociatedScope());
		func.body.accept(this);
		table.close();
	}

	@Override
	public void visitTopLevel(Tree.TopLevel program) 
	{
		table.open(program.globalScope);
		for (Tree.ClassDef cd : program.classes) 
		{
			cd.accept(this);
		}
		table.close();
	}

	@Override
	public void visitBlock(Tree.Block block) 
	{
		table.open(block.associatedScope);
		for (Tree s : block.block) 
		{
			s.accept(this);
		}
		table.close();
	}

	@Override
	public void visitAssign(Tree.Assign assign) 
	{		
		assign.left.accept(this);
		assign.expr.accept(this);
		if(assign.left.lvKind != Tree.LValue.Kind.ARRAY_ELEMENT && assign.left.type != BaseType.ERROR && currentFunction.isStatik() && ((Tree.Ident)assign.left).symbol.isMemberVar())
		{
			issueError(new RefNonStaticError(assign.left.getLocation(),currentFunction.getName(),((Tree.Ident)assign.left).name));
			assign.type = BaseType.ERROR;
		}
		else
		{
			assign.type = BaseType.ERROR;
		}
	}

	@Override
	public void visitBreak(Tree.Break breakStmt) 
	{
		if(breaks.empty())
		{
			issueError(new BreakOutOfLoopError(breakStmt.getLocation()));
			breakStmt.type = BaseType.ERROR;
		}
	}

	@Override
	public void visitForLoop(Tree.ForLoop forLoop) 
	{
		checkTestExpr(forLoop.condition);
		breaks.add(forLoop);
		if (forLoop.loopBody != null) 
		{
			forLoop.loopBody.accept(this);
		}
		breaks.pop();
	}

	@Override
	public void visitIf(Tree.If ifStmt) 
	{
		checkTestExpr(ifStmt.condition);
		if (ifStmt.trueBranch != null) 
		{
			ifStmt.trueBranch.accept(this);
		}
		if (ifStmt.falseBranch != null) 
		{
			ifStmt.falseBranch.accept(this);
		}
	}

	@Override
	public void visitPrint(Tree.Print printStmt) 
	{
		int i = 0;
		for (Tree.Expr e : printStmt.exprs) 
		{
			e.accept(this);
			i++;
			if (!e.type.equal(BaseType.ERROR) && !e.type.equal(BaseType.BOOL) && !e.type.equal(BaseType.INT) && !e.type.equal(BaseType.STRING)) 
			{
				issueError(new BadPrintArgError(e.getLocation(), Integer.toString(i), e.type.toString()));
			}
		}
	}

	@Override
	public void visitReturn(Tree.Return returnStmt) 
	{
		returnStmt.expr.accept(this);
		if(!returnStmt.expr.type.compatible(currentFunction.getReturnType()))
		{
			issueError(new BadReturnTypeError(returnStmt.getLocation(),currentFunction.getReturnType().toString(),returnStmt.expr.type.toString()));
			returnStmt.type = BaseType.ERROR;
		}
	}

	@Override
	public void visitWhileLoop(Tree.WhileLoop whileLoop) 
	{
		checkTestExpr(whileLoop.condition);
		breaks.add(whileLoop);
		if (whileLoop.loopBody != null) 
		{
			whileLoop.loopBody.accept(this);
		}
		breaks.pop();
	}

	@Override
	public void visitTypeIdent(Tree.TypeIdent type) 
	{
		switch (type.typeTag) 
		{
			case Tree.VOID:
				type.type = BaseType.VOID;
				break;
			case Tree.INT:
				type.type = BaseType.INT;
				break;
			case Tree.BOOL:
				type.type = BaseType.BOOL;
				break;
			default:
				type.type = BaseType.STRING;
		}
	}

	@Override
	public void visitTypeClass(Tree.TypeClass typeClass) 
	{
		Class c = table.lookupClass(typeClass.name);
		if (c == null) 
		{
			issueError(new ClassNotFoundError(typeClass.getLocation(),typeClass.name));
			typeClass.type = BaseType.ERROR;
		} 
		else 
		{
			Class parent = c.getParent();
			typeClass.type = c.getType();;
		}
	}

	@Override
	public void visitTypeArray(Tree.TypeArray typeArray) 
	{
		typeArray.elementType.accept(this);
		typeArray.type = typeArray.elementType.type;
	}

	private void issueError(DecafError error) 
	{
		Driver.getDriver().issueError(error);
	}

	private void checkTestExpr(Tree.Expr expr) 
	{
		expr.accept(this);
		if (!expr.type.equal(BaseType.ERROR) && !expr.type.equal(BaseType.BOOL)) 
		{
			issueError(new BadTestExpr(expr.getLocation()));
		}
	}
}
